package com.project.gobang.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.project.gobang.constants.Constants;
import com.project.gobang.game.MatchRequest;
import com.project.gobang.game.MatchResponse;
import com.project.gobang.game.Matcher;
import com.project.gobang.game.OnlineUserManager;
import com.project.gobang.model.UserInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

// 通过这个类来处理匹配功能中的 websocket 请求
@Component
public class MatchAPI extends TextWebSocketHandler {
    // 处理JSON数据要用到的类
    private ObjectMapper objectMapper = new ObjectMapper();

    @Autowired
    private OnlineUserManager onlineUserManager;

    @Autowired
    private Matcher matcher;

    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        // 该函数表示用户和服务器连接之后, 要执行的代码
        // 玩家上线, 加入到 OnlineUserManager 中

        // 1. 先获取到当前用户的身份信息(谁连入了游戏大厅, 就要把这个用户获取到)
        // 注意, 这里拿到的 user 是可能为空的, 因为我们还没有使用拦截器.
        // 如果用户并没有通过 login.html 来进行登录, 而是直接访问 game_hall.html, 此时 user 就会为 null
        // 这里可以使用拦截器, 也可以用 try-catch, 看具体业务需求
        try {
            UserInfo user = (UserInfo) session.getAttributes().get(Constants.USER);
            // 2. 在添加进维护用户在线状态的哈希表之前, 要先判定当前用户是否已经是在线状态
            // 拿取用户的 session, 观察是否已经登录过了
            WebSocketSession hallOnlineStatus = onlineUserManager.getUserOnlineStatusInHallSession(user.getUserId());
            WebSocketSession roomOnlineStatus = onlineUserManager.getUserOnlineStatusInRoomSession(user.getUserId());
            if (hallOnlineStatus != null || roomOnlineStatus != null) {
                // 如果一个账号, 游戏大厅在线, 游戏房间还在线, 说明他多开了, 这也不行
                // 如果当前用户的 WebSocketSession 不为空, 说明用户之前已经登录过了, 并且还没下线
                // 针对这个情况就要告知服务器用户重复登录, 并且由服务器来关闭这次连接
                MatchResponse response = new MatchResponse();

                // 这里如果设置成false, 前端代码又要修改, 因此这里设置成true
                // false就表示意外之外的情况, true表示问题是可预料的, 这样来理解
                response.setSuccess(true);
                response.setReason("禁止重复登录");
                response.setMessage("repeatConnection");

                // 给前端返回这次响应内容
                session.sendMessage(new TextMessage(objectMapper.writeValueAsString(response)));
                
                // 由服务器来关闭连接(注意: 这里关闭的是本次请求过来的连接(即 把 浏览器2 发送过来的会话给关掉, 并不影响 浏览器1))
                // 调用 .close() 方法, 也会触发下面的 "关闭连接" websocket 相关方法. 需要去修改
                // 这里不应该直接关闭连接, 应该返回一个处理过的响应, 供客户端来选择执行策略
                // session.close();
                return;
            }


            // 3. 拿到用户信息后, 设置玩家的在线状态.
            // 注意: 这里的 session 是 WebSocketSession, 里面的信息是从 HttpSession 中拿过来的.
            onlineUserManager.enterGameHall(user.getUserId(), session);
            System.out.println("玩家:" + user.getUsername() + " 进入游戏大厅");
        }catch (NullPointerException e) {
            e.printStackTrace();
            // 如果出现空指针异常, 说明当前用户的身份信息是空的.
            // 根据之前前后端约定, 需要把当前用户尚未登录这个信息给返回回去
            // 那么就需要构建一个类来表示 WebSocket响应, 既然响应有了, 同时也构建一个类来表示 WebSocket请求

            // 这里就构建一个响应传给前端
            MatchResponse response = new MatchResponse();
            response.setSuccess(false);
            response.setReason("用户并未登录");
            session.sendMessage(new TextMessage(objectMapper.writeValueAsString(response)));
        }

    }

    @Override
    public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {
        // 收到 websocket 数据之后执行的代码
        // 处理 开始匹配请求 和 停止匹配请求.

        // 1. 获取到客户端给服务器发送的数据
        String payload = message.getPayload().toString();
        // 当前获取到的数据 payload 是一个 JSON 格式的字符串, 需要将它转成 Java 对象
        MatchRequest request = objectMapper.readValue(payload, MatchRequest.class);

        // 2. response 就要根据客户端发来的请求数据, 构建响应, 然后返回
        MatchResponse response = new MatchResponse();

        // 3. 拿到对应的玩家信息, 之后要将玩家加入到匹配队列中
        UserInfo user = (UserInfo) session.getAttributes().get(Constants.USER);
        if (request.getMessage().equals("startMatch")) {
            // 进入 匹配队列
            // 这里需要创建一个类来表示匹配队列, 把当前用户加入其中
            matcher.add(user);
            // 玩家信息加入到匹配队列中后, 就可以返回响应给客户端了
            response.setSuccess(true);
            response.setMessage("startMatch");

            // 将 Java对象 转换成 JSON格式字符串
            String json = objectMapper.writeValueAsString(response);
            // 再将 JSON 包装成 WebSocketMessage, 发送给前端
            session.sendMessage(new TextMessage(json));

        } else if (request.getMessage().equals("stopMatch")) {
            // 退出 匹配队列
            // 同理, 这里也需要创建一个类来表示匹配队列, 把当前用户从队列中删除
            matcher.remove(user);
            // 删除之后, 返回响应(ACK)
            response.setSuccess(true);
            response.setMessage("stopMatch");

            // 将 Java对象 转换成 JSON格式字符串
            String json = objectMapper.writeValueAsString(response);
            // 再将 JSON 包装成 WebSocketMessage, 发送给前端
            session.sendMessage(new TextMessage(json));
        } else {
            // 其他非法情况
            response.setSuccess(false);
            response.setMessage("非法的匹配请求");

            // 将 Java对象 转换成 JSON格式字符串
            String json = objectMapper.writeValueAsString(response);
            // 再将 JSON 包装成 WebSocketMessage, 发送给前端
            session.sendMessage(new TextMessage(json));
        }
    }

    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
        // 连接出现异常会执行的代码
        // 玩家下线, 从 OnlineUserManager 中删除(连接异常玩家也会下线)

        try {
            // 1. 同样的: 先获取到当前用户的身份信息
            UserInfo user = (UserInfo) session.getAttributes().get(Constants.USER);

            // 2. 将表示上下线的哈希表中, 将对应的下线用户的给 remove 掉
            WebSocketSession tmpSession = onlineUserManager.getUserOnlineStatusInHallSession(user.getUserId());
            if (tmpSession == session) {
                // 只有是同一个浏览器的用户下线, 才能执行这一代码
                onlineUserManager.exitGameHall(user.getUserId());
            }

            // 如果玩家正在匹配中, 而客户端和服务器的连接发生异常, 也需要将该玩家移除匹配队列
            if (tmpSession == session) {
                // 只有当同一个浏览器断开连接时, 才会将玩家移除出匹配队列
                matcher.remove(user);
            }
        }catch (NullPointerException e) {
            e.printStackTrace();

            // 同上理由
            // 用户离开 game_hall.html 后, 连接已经断开, 这里不应该再尝试给客户端发送消息
//            MatchResponse response = new MatchResponse();
//            response.setSuccess(false);
//            response.setReason("用户并未登录");
//            session.sendMessage(new TextMessage(objectMapper.writeValueAsString(response)));
        }
    }

    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        // 连接关闭后执行的代码
        // 玩家下线, 从 OnlineUserManager 中删除

        // ps: 在连接方法中处理多开问题, 调用 .close() 方法后, 也会立即触发这个方法, 然后删除哈希表里面的用户在线状态
        // 虽然针对 浏览器2 的用户登录并没有插入哈希表中, 但是这里会将 浏览器1 的用户也给踢掉, 因为两者的 userId 都是相同的
        // 所以这里也要进行特殊处理
        try {
            // 同理
            UserInfo user = (UserInfo) session.getAttributes().get(Constants.USER);
            WebSocketSession tmpSession = onlineUserManager.getUserOnlineStatusInHallSession(user.getUserId());
            if (tmpSession == session) {
                // 注意: 此处的 session 是浏览器2 建立连接时创建的 WebSocket 会话, tmpSession 是浏览器1 登录时存储的 WebSocketSession, 两者是不同的
                // 因此, 需要两个 session 相同(即, 只有是同一个浏览器的用户下线, 才能执行这一代码)
                onlineUserManager.exitGameHall(user.getUserId());
                System.out.println("玩家: " + user.getUsername() + "下线");
            }

            // 如果玩家正在匹配中, 而他不想匹配, 直接把浏览器一关, 此时也需要把该玩家移除匹配队列
            if (tmpSession == session) {
                // 只有当同一个浏览器断开连接时, 才会将玩家移除出匹配队列
                matcher.remove(user);
            }
        } catch (NullPointerException e) {
            e.printStackTrace();

            // 同上理由
//            MatchResponse response = new MatchResponse();
//            response.setSuccess(false);
//            response.setReason("用户并未登录");
//            session.sendMessage(new TextMessage(objectMapper.writeValueAsString(response)));
        }
    }
}
